#include <linux/module.h>		// module_init  module_exit  函数头文件
#include <linux/init.h>			// __init   __exit   宏头文件     路径： kernel/include/linux/init.h
#include <linux/fs.h>           // register_chrdev 
// 模块安装函数，static  chrdev_init 不被内核里面其他的.c 调用，只能被宏调用  

// #include <linux/uaccess.h>
#include <asm-generic/uaccess.h>    // copy_to_user  ,  copy_from_user  函数


#include <linux/types.h>
#include <linux/ioctl.h>

/**
 * 就api接口，设备号动态注册，静态注册
 *  file_operation结构体
 *  copy_from_user、copy_to_user 使用
 */


// mknod  /dev/driver_test  c 200 0   
//cat /proc/devices|grep 200



static unsigned int MYMAJOR = 200;
static const char * MYNAME = "testchar";

static char kbuf[100]={0};			// 内核空间的buf

static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release\n");
	
	return 0;
}

static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
	// 但是现在暂时我们写不了这么多，所以用一个printk打印个信息来做代表。
	printk(KERN_INFO "test_chrdev_open\n");


	
	return 0;
}

// 写函数的本质就是将应用层传递过来的数据先复制到内核中，然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
	size_t count, loff_t *ppos)
{
		int ret = -1;
	printk(KERN_INFO "test_chrdev_write\n");	
	
	//  应用空间的ubuf ， kbuf 内核空间的buf ,要拷贝长度 ,  从 用户空间ubuf 复制到内核空间 kbuf 
	//   大于0拷贝失败， 0成功
	//memcpy(kbuf, ubuf);		// 不行，因为2个不在一个地址空间中

	ret = copy_from_user(kbuf, ubuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_from_user fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "copy_from_user success..\n");
	return 0;
}


ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	
	int ret = -1;
	printk(KERN_INFO "test_chrdev_read\n");
	
    //  从内核空间空间kbuf           复制到用户空间ubuf   ， count复制的长度 
    ret = copy_to_user(ubuf, kbuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_to_user fail\n");
		return -EINVAL;
	}


	return 0;
}

// 自定义一个file_operations结构体变量，并且去填充， 初始化结构体就是
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,				// 惯例，直接写即可
	
	.open		= test_chrdev_open,			// 将来应用open打开这个设备时实际调用的
	.release	= test_chrdev_release,		// 就是这个.close对应的函数
	.write 		= test_chrdev_write,
	.read		= test_chrdev_read,
};



static int __init chrdev_init(void)
{	
	int ret = -1;
	printk(KERN_INFO "chrdev_init helloworld init\n");
	//printk("<7>" "chrdev_init helloworld init\n");

	/* 
    register_chrdev  注册设备号2种方式:
	 静态注册：
	 *  传入主设备号， 
	 *  返回值：返回0 成功,  负数表示失败,返回错误码，比如-EINVAL
	 名称 在ls的时候可以看到
	 */
    
//  register_chrdev("主设备号，写0系统给我们自动分配"，名称，,file_operation结构)
	// 在module_init宏调用的函数中去注册字符设备驱动

	ret = register_chrdev(MYMAJOR, MYNAME, &test_fops);
	
	if (ret)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		///kernel/kernel/include/asm-generic/errno-base.h
		///kernel/kernel/include/asm-generic/errno.h
		return -EINVAL;   //返回响应错误码，非法参数，比如用别人用过的主设别号
	}
	printk(KERN_INFO "register_chrdev success.  id= %d \n",ret );


    //动态注册: 
	// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
	// 内核如果成功分配就会返回分配的主设备好；如果分配失败会返回负数
#if 0
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
#endif   

	return 0;
}



// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");



/*
  驱动异常：
	如果内核执行下面代码，抛异常，不能卸载.ko ，只能重启设备
	

	int *p = NULL;
	*p =100;
	printk("call %s() @ %d\n", __FUNCTION__, __LINE__);  
	printk("*p=%d\n", *p);
	*/

	

	// 在module_exit宏调用的函数中去注销字符设备驱动
	unregister_chrdev(MYMAJOR, MYNAME);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// linux 内核是开源的，你自己的模块也要GPL, 否则调用内核出现莫名奇妙错误有可能
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息








